package idgenerator

import (
    "hash/fnv"
    "log"
    "sync"
    "time"
)

const (
    workerIdBits     int64 = 5
    datacenterIdBits int64 = 5
    sequenceBits     int64 = 12

    maxWorkerId     int64 = -1 ^ (-1 << uint64(workerIdBits))
    maxDatacenterId int64 = -1 ^ (-1 << uint64(datacenterIdBits))
    maxSequence     int64 = -1 ^ (-1 << uint64(sequenceBits))

    timeLeft uint8 = 22
    dataLeft uint8 = 17
    workLeft uint8 = 12

    twepoch int64 = 1525705533000
)

type IdGenerator struct {
    mu           *sync.Mutex
    lastStamp    int64
    workerId     int64
    dataCenterId int64
    sequence     int64
}

func MakeGenerator(cluster string, node string) *IdGenerator {
    fnv64 := fnv.New64()
    _, _ = fnv64.Write([]byte(cluster))
    dataCenterId := int64(fnv64.Sum64())

    fnv64.Reset()
    _, _ = fnv64.Write([]byte(node))
    workerId := int64(fnv64.Sum64())

    return &IdGenerator{
        mu:           &sync.Mutex{},
        lastStamp:    -1,
        dataCenterId: dataCenterId,
        workerId:     workerId,
        sequence:     1,
    }
}

func (w *IdGenerator) getCurrentTime() int64 {
    return time.Now().UnixNano() / 1e6
}

func (w *IdGenerator) NextId() int64 {
    w.mu.Lock()
    defer w.mu.Unlock()

    timestamp := w.getCurrentTime()
    if timestamp < w.lastStamp {
        log.Fatal("can not generate id")
    }
    if w.lastStamp == timestamp {
        w.sequence = (w.sequence + 1) & maxSequence
        if w.sequence == 0 {
            for timestamp <= w.lastStamp {
                timestamp = w.getCurrentTime()
            }
        }
    } else {
        w.sequence = 0
    }
    w.lastStamp = timestamp

    return ((timestamp - twepoch) << timeLeft) | (w.dataCenterId << dataLeft) | (w.workerId << workLeft) | w.sequence
}

func (w *IdGenerator) tilNextMillis() int64 {
    timestamp := w.getCurrentTime()
    if timestamp <= w.lastStamp {
        timestamp = w.getCurrentTime()
    }
    return timestamp
}
